/*
 * TODO language 
 * Copyright (c) 2010 TODO Team
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#include <iostream>
#include <cstdlib>
#include <string>
#include <cmath>
#include "position.hh"
#include "location.hh"
#include "typeman/API.h"
#include "internal_fwd.h"
#include "compiler_fwd.h"
#include "modules/module.h"
#include "modules/std/core.h"

namespace todo {
	
Core core_module;

// Compiled files counter
int Compiler::count = 0;

// Opcode list
std::vector<todo::opcode::Opcode*> Compiler::opcode_list;
	
Compiler::Compiler()
{
	// Global scope
	scopes.add(new Scope("."));
	scopes.set_global(scopes.Cscope);
	
	blk_states.push(blk_state());
}

void Compiler::init(void)
{
	if (Compiler::count++) {
		return;
	}
	
	// Standard modules
	modules.add(&core_module);
}

/**
 * Display the error message
 * @param message the error message
 */
void Compiler::error(const char* message) const
{
	yy::position last;
	
	if (location) {
		last = location->end - 1;

		if (is_file) {
			std::cout << "Compile Error: " << message << " in " << *last.filename << " on line " << last.line << std::endl;
		} else {
			std::cout << "Compile Error: " << message << " on line " << last.line << std::endl;
		}
	} else {
		std::cout << "Compile Error: " << message << std::endl;
	}
	exit(1);
}

inline void Compiler::error(std::string message) const
{
	error(message.c_str());
}

/**
 * Add an opcode the list
 * @param opcode the opcode to be added
 */
inline void Compiler::addOpcode(todo::opcode::Opcode* opcode)
{
	// Sets the op number
	opcode->op_num = opcode_list.size();

	opcode_list.push_back(opcode);
}

/**
 * Checks if a function already was registered
 * @param name the function name
 */
bool Compiler::check_func_exists(const todo::Data& name)
{
	return (scopes.Gscope->functions.find(STR_V(name.data)) == scopes.Gscope->functions.end()) ? false : true;
}

/**
 * Registers a function
 * @param type the return type
 * @param name the function name
 */
void Compiler::add_func(todo::Data* type, todo::Data& name)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_DECLARE_FUNC);
	
	if (check_func_exists(name)) {
		error("Cannot redeclare function `" + STR_V(name.data) + "'");
	}
	register_func(new Function(STR_P(name.data), get_op_num(), 0, NULL));
	
	if (type) {
		opcode->set_op1(*type);
	}
	opcode->set_op2(name);
	opcode->set_handler(&vm::Engine::add_func);
	opcode->info = decl_args.top();
	name.extra = get_op_num();
	
	// Create a new scope
	scopes.add(new Scope);
	
	decl_args.pop();
	
	addOpcode(opcode);	
}

void Compiler::negative(todo::Data& expr)
{	
	long val = INT_V(expr.data);
	
	if (expr.data_type == TODO_CONSTANT) {
		INT_V(expr.data) = 0;
		INT_V(expr.data) -= val;
	}
}

void Compiler::end_func(todo::Data& func)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_END_FUNC);
	
	get_opcode(func.extra)->op2.extra = get_op_num();
	opcode->set_handler(&vm::Engine::end_func);
	
	// Drop the function scope
	scopes.pop();
	
	addOpcode(opcode);	
}

void Compiler::func_call(Data& name, Data& result)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_FUNC_CALL);
	
	name.result = new ValueResult;
	name.result_flag = todo::NONREFERENCED;
	opcode->set_op1(name);
	opcode->set_handler(&vm::Engine::func_call);
	opcode->info = call_args.top();
	
	SET_RESULT(result, name.result);
	
	addOpcode(opcode);	
}

void Compiler::start_loop(todo::Data& expr)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_JMPZ);
	todo::Data jmp_op(TODO_CONSTANT, 0);
	
	expr.extra = get_op_num();
	opcode->set_op1(expr);
	opcode->set_op2(jmp_op);
	opcode->set_handler(&vm::Engine::if_cond);
	
	addOpcode(opcode);
}

void Compiler::start_loop_2(todo::Data& expr)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_JMPZ_EX);
	todo::Data jmp_op(TODO_CONSTANT, 0);
	
	expr.extra = get_op_num();

	opcode->set_op1(expr);
	opcode->set_op2(jmp_op);
	opcode->set_handler(&vm::Engine::jmpz_ex);
	
	addOpcode(opcode);
}

void Compiler::pos_loop_next(Data& begin, Data& expr)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_JMP);
	Data jmp_op(TODO_CONSTANT);
	
	get_opcode(expr.extra)->op1.extra = get_op_num();
	jmp_op.extra = begin.extra-1;
		
	opcode->set_op1(jmp_op);
	opcode->set_handler(&vm::Engine::jmp);
	
	addOpcode(opcode);
}

void Compiler::end_loop_2(Data& expr, Data& begin, Data& op3)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_JMP);
	todo::Data jmp_op(TODO_CONSTANT);
	
	get_opcode(expr.extra)->op2.extra = get_op_num();
	jmp_op.extra = expr.extra;
	
	opcode->set_op1(jmp_op);	
	opcode->set_handler(&vm::Engine::jmp);
	
	pop_brk_jmps();
	
	addOpcode(opcode);	
}

void Compiler::begin_loop(todo::Data& expr)
{
	expr.extra = get_op_num();
	
	brk_jmps.push(std::stack<opcode::Opcode*>());
}

void Compiler::end_loop(todo::Data& expr, todo::Data& begin)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_JMP);
	todo::Data jmp_op(TODO_CONSTANT);
	
	get_opcode(expr.extra)->op2.extra = get_op_num();
	jmp_op.extra = begin.extra-1;
	
	opcode->set_op1(jmp_op);	
	opcode->set_handler(&vm::Engine::jmp);
	
	pop_brk_jmps();
	
	addOpcode(opcode);	
}

void Compiler::pop_brk_jmps(void)
{
	std::stack<opcode::Opcode*> brk_tmp;
	opcode::Opcode* op_ptr;
	
	while (!brk_jmps.top().empty()) {
		op_ptr = brk_jmps.top().top();
		
		// Check if we are in the nesting level indicated in the break N
		if (op_ptr->op2.data && (unsigned)INT_V(op_ptr->op2.data) != brk_jmps.size()) {
			brk_tmp.push(op_ptr);
		} else {
			op_ptr->op1.extra = get_op_num();
		}
		brk_jmps.top().pop();
	}
	brk_jmps.pop();
	
	while (!brk_tmp.empty()) {
		brk_jmps.top().push(brk_tmp.top());
		brk_tmp.pop();
	}		
}

/**
 * Generates opcode to creates a new vector
 */
void Compiler::new_vector(todo::Data& result)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_VECTOR);
	todo::Data vec(TODO_DYNAMIC, 0);

	vec.result = new ValueResult;

	opcode->set_op1_(vec);
	opcode->set_handler(&vm::Engine::new_vector);
	
	SET_RESULT(result, vec.result);
		
	addOpcode(opcode);
}

/**
 * Generates opcode to add an item into a vector
 * @param vector the vector
 * @param item the item
 */
void Compiler::vector_add_item(todo::Data& vector, todo::Data& item)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_VECTOR_ADD);
	
	opcode->set_op1(vector);
	opcode->set_op2(item);
	opcode->set_handler(&vm::Engine::vector_add);
	
	addOpcode(opcode);
}

/**
 * Generates opcode to create a map
 */
void Compiler::new_map(todo::Data& result)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_MAP);
	todo::Data vec(TODO_DYNAMIC, 0);

	vec.result = new ValueResult;
	opcode->set_op1_(vec);
	opcode->set_handler(&vm::Engine::new_map);
	
	SET_RESULT(result, vec.result);
		
	addOpcode(opcode);
}

/**
 * Generates opcode to add a item into a map
 */
void Compiler::map_add_item(todo::Data& map, todo::Data& key, todo::Data& item)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_MAP_ADD);
	
	key.result = map.result;
	key.result_flag = REFERENCED;
	key.data_type = TODO_CONSTANT;
	opcode->set_op1(key);
	opcode->set_op2(item);
	opcode->set_handler(&vm::Engine::map_add);
	
	addOpcode(opcode);
}

/**
 * Generates opcode to print/println instruction
 * @param op the value to be printed
 * @param newline add a implict newline
 */
void Compiler::println(todo::Data& op, bool newline)
{
	opcode::Opcode* opcode = new opcode::Opcode;
	/*
	if (op.data_type == TODO_VAR && !check_exists_var(STR_P(op.data))) {
		error(std::string("Variable `") + STR_V(op.data) + "' not found!");
	}*/
	
	opcode->set_op1(op);
	if (newline) {
		opcode->set_type(opcode::TODO_PRINTLN);
		opcode->set_handler(&todo::vm::Engine::println);
	} else {
		opcode->set_type(opcode::TODO_PRINT);
		opcode->set_handler(&todo::vm::Engine::print);
	}
	
	addOpcode(opcode);
}

void Compiler::static_method_call(todo::Data& object, todo::Data& method_name, todo::Data& result)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_STATIC_METHOD_CALL);
	todo::ValueResult *result_ptr;
	
	if (object.data_type == todo::TODO_DYNAMIC) {
		result_ptr = object.result;		
	} else {
		result_ptr = new todo::ValueResult;
	}
	
	result.data_type = todo::TODO_DYNAMIC;
	result.result = result_ptr;
	object.result = result_ptr;
	
	opcode->set_op1(object);
	opcode->set_op2(method_name);
	opcode->set_handler(&todo::vm::Engine::static_method_call);
	opcode->info = call_args.top();
	
	call_args.pop();
	
	addOpcode(opcode);	
}

void Compiler::method_call(todo::Data& object, todo::Data& method_name, todo::Data& result)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_METHOD_CALL);
	todo::ValueResult *result_ptr;

	if (object.data_type == todo::TODO_DYNAMIC) {
		result_ptr = object.result;		
	} else {
		result_ptr = new todo::ValueResult;
	}
	
	result.data_type = todo::TODO_DYNAMIC;
	result.result = result_ptr;
	object.result = result_ptr;

	opcode->set_op1(object);
	opcode->set_op2(method_name);
	opcode->set_handler(&todo::vm::Engine::method_call);
	opcode->info = call_args.top();
	
	call_args.pop();
	
	addOpcode(opcode);	
}

void Compiler::begin_fcall(void)
{
	call_args.push(new ArgList);
}

void Compiler::begin_fdecl(void)
{
	decl_args.push(new FunctionArgList);
}

void Compiler::add_arg(todo::Data& data)
{
	call_args.top()->args.push_back(new todo::Data(data));
}

void Compiler::add_decl_arg(todo::Data& type, todo::Data& name)
{
	decl_args.top()->args.push_back(new FunctionArg(STR_V(name.data), type.type));
	
	name.data->type->dealloc(name.data);
}

/**
 * Checks if a variable already exists
 * @param var_name the variable name
 */
void Compiler::check_exists_var(Data* module, Data& var, Data& result)
{/*
	variable_map::iterator it = current_scope->variables.find(*var_name);
	
	return (it != current_scope->variables.end());
	*/
}

/**
 * Create a new scope
 */
void Compiler::add_module(Data& name, Data& result)
{
	scopes.add(new Scope(STR_V(name.data)));
}

void Compiler::end_module(Data& result)
{

}

inline void Compiler::register_func(Function* func)
{
	scopes.Gscope->functions.insert(std::pair<std::string, Function*>(*func->name, func));
}

/**
 * Try to find a variable in the current scope
 */
inline Var* Compiler::get_var(const std::string* var_name)
{
	variable_map::iterator it = scopes.Cscope->variables.find(*var_name);
	
	if (it != scopes.Cscope->variables.end()) {
		return it->second;
	}	
	return NULL;
}

/**
 * Check if the value is compatible with the var type
 * @param var_name the variable name
 * @param value the value to be checked
 */
inline bool Compiler::check_type_var(const std::string* var_name, const todo::Data& value)
{
	Var* var = get_var(var_name);
	
	if (var == NULL) {
		error(std::string("Variable named `") + *var_name + "' not found!");
	}
	
	if (value.data_type == todo::TODO_VAR) {
		Var* var_value = get_var(STR_P(value.data));
		
		if (var_value == NULL) {
			error(std::string("Variable named `") + STR_V(value.data) + "' not found!");
		}
		return (var->type == var_value->type);
	} else if (value.data_type == todo::TODO_DYNAMIC) {
		return true;
	}

	return (var->type == value.data->type);
}

/**
 * Check if the value is compatible with the type
 * @param type the expected type
 * @param value the value to fetch the type
 */
inline bool Compiler::check_type_value(const Type* type, const todo::Data& value)
{
	if (value.data_type == todo::TODO_VAR) {
		Var* var = get_var(STR_P(value.data));
		
		if (var == NULL) {
			error(std::string("Variable named `") + STR_V(value.data) + "' not found!");
		}
		return (var->type == type);
	} else if (value.data_type == todo::TODO_CONSTANT) {
		return (value.data->type == type);
	}
	return true;
}

/**
 * Declares a variable
 * @param decl_type the variable type
 * @param var the variable name
 * @param value the value to be assigned
 */
void Compiler::declare_var(Data& decl_type, Data& var, Data* value)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_DECLARE_VAR);

	// The variable name
	opcode->set_op1(var);
	// Initialization value
	if (value) {
		opcode->set_op2(*value);
	} else {
		Data tmp_data(TODO_CONSTANT, decl_type.type->alloc(), 0);
		// Sets the initial value according to variable type
		opcode->set_op2(tmp_data);
	}
	opcode->set_handler(&vm::Engine::declare_var);
	addOpcode(opcode);
	
	// Register a new variable
	scopes.add_var(new Var(STR_V(var.data), decl_type.type, value));
}

/**
 * Pre-increments variable
 */
void Compiler::pre_increment(todo::Data& var)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_PRE_INC);
	
	var.result = new ValueResult;
	
	opcode->set_op1(var);
	opcode->set_handler(&vm::Engine::pre_increment);
	
	addOpcode(opcode);
}

/**
 * Pre-decrements variable
 */
void Compiler::pre_decrement(todo::Data& var)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_PRE_DEC);
	
	var.result = new ValueResult;
	
	opcode->set_op1(var);
	opcode->set_handler(&vm::Engine::pre_decrement);
	
	addOpcode(opcode);
}

/**
 * Pos-increments variable
 */
void Compiler::pos_increment(todo::Data& var)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_POS_INC);
	
	var.result = new ValueResult;
	
	opcode->set_op1(var);
	opcode->set_handler(&vm::Engine::pos_increment);
	
	addOpcode(opcode);
}

/**
 * Pos-decrements variable
 */
void Compiler::pos_decrement(todo::Data& var)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_POS_DEC);
	
	var.result = new ValueResult;
	
	opcode->set_op1(var);
	opcode->set_handler(&vm::Engine::pos_decrement);
	
	addOpcode(opcode);
}

void Compiler::subscript(todo::Data& obj, todo::Data& key)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_SUBSCRIPT);
	
	obj.result = new ValueResult;
	
	opcode->set_op1(obj);
	opcode->set_op2(key);
	opcode->set_handler(&vm::Engine::subscript);
	
	addOpcode(opcode);
}

void Compiler::or_expr(Data& expr1, Data& expr2, Data* result, bool assign)
{
	opcode::Opcode* opcode = new opcode::Opcode;
	
	if (assign == false) {
		/**
		 * Only creates the temporary result space for non-assigning expression
		 * e.g. x | y
		 */
		NEW_RESULT_IF_NOT_EXISTS(expr1);
		SET_RESULT(*result, expr1.result);
	}
	opcode->set_type(assign ? opcode::TODO_ASSIGN_BW_OR : opcode::TODO_BW_OR);
	opcode->set_op1(expr1);
	opcode->set_op2(expr2);
	opcode->set_handler(&vm::Engine::bw_or_expr);
	
	addOpcode(opcode);	
}

void Compiler::and_expr(Data& expr1, Data& expr2, Data* result, bool assign)
{
	opcode::Opcode* opcode = new opcode::Opcode;
	
	if (assign == false) {
		/**
		 * Only creates the temporary result space for non-assigning expression
		 * e.g. x & y
		 */
		NEW_RESULT_IF_NOT_EXISTS(expr1);
		SET_RESULT(*result, expr1.result);
	}
	opcode->set_type(assign ? opcode::TODO_ASSIGN_BW_AND : opcode::TODO_BW_AND);
	opcode->set_op1(expr1);
	opcode->set_op2(expr2);
	opcode->set_handler(&vm::Engine::bw_and_expr);
	
	addOpcode(opcode);	
}

void Compiler::xor_expr(Data& expr1, Data& expr2, Data* result, bool assign)
{
	opcode::Opcode* opcode = new opcode::Opcode;
	
	if (assign == false) {
		/**
		 * Only creates the temporary result space for non-assigning expression
		 * e.g. x ^ y
		 */
		NEW_RESULT_IF_NOT_EXISTS(expr1);
		SET_RESULT(*result, expr1.result);
	}
	opcode->set_type(assign ? opcode::TODO_ASSIGN_XOR : opcode::TODO_XOR);
	opcode->set_op1(expr1);
	opcode->set_op2(expr2);
	opcode->set_handler(&vm::Engine::xor_expr);
	
	addOpcode(opcode);	
}

void Compiler::shift_left(Data& expr1, Data& expr2, Data* result, bool assign)
{
	opcode::Opcode* opcode = new opcode::Opcode;
	
	if (assign == false) {
		/**
		 * Only creates the temporary result space for non-assigning expression
		 * e.g. x << y
		 */
		NEW_RESULT_IF_NOT_EXISTS(expr1);
		SET_RESULT(*result, expr1.result);
	}
	opcode->set_type(assign ? opcode::TODO_ASSIGN_SL : opcode::TODO_SL);
	opcode->set_op1(expr1);
	opcode->set_op2(expr2);
	opcode->set_handler(&vm::Engine::shift_left);
	
	addOpcode(opcode);	
}

void Compiler::shift_right(Data& expr1, Data& expr2, Data* result, bool assign)
{
	opcode::Opcode* opcode = new opcode::Opcode;
	
	if (assign == false) {
		/**
		 * Only creates the temporary result space for non-assigning expression
		 * e.g. x >> y
		 */
		NEW_RESULT_IF_NOT_EXISTS(expr1);
		SET_RESULT(*result, expr1.result);
	}
	opcode->set_type(assign ? opcode::TODO_ASSIGN_SR : opcode::TODO_SR);
	opcode->set_op1(expr1);
	opcode->set_op2(expr2);
	opcode->set_handler(&vm::Engine::shift_right);
	
	addOpcode(opcode);	
}

void Compiler::less(todo::Data& operand1, todo::Data& operand2)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_LESS);
	
	if (operand1.data_type != todo::TODO_DYNAMIC) {
		operand1.result = new ValueResult;	
	}
	opcode->set_op1(operand1);
	opcode->set_op2(operand2);
	opcode->set_handler(&vm::Engine::less);
	
	addOpcode(opcode);
}

void Compiler::greater(todo::Data& operand1, todo::Data& operand2)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_GREATER);
	
	if (operand1.data_type != todo::TODO_DYNAMIC) {
		operand1.result = new ValueResult;	
	}
	opcode->set_op1(operand1);
	opcode->set_op2(operand2);
	opcode->set_handler(&vm::Engine::greater);
	
	addOpcode(opcode);
}

void Compiler::greater_equal(todo::Data& operand1, todo::Data& operand2)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_GREATER_EQUAL);
	
	if (operand1.data_type != todo::TODO_DYNAMIC) {
		operand1.result = new ValueResult;	
	}
	opcode->set_op1(operand1);
	opcode->set_op2(operand2);
	opcode->set_handler(&vm::Engine::greater_equal);
	
	addOpcode(opcode);
}

void Compiler::less_equal(todo::Data& operand1, todo::Data& operand2)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_LESS_EQUAL);
	
	if (operand1.data_type != todo::TODO_DYNAMIC) {
		operand1.result = new ValueResult;	
	}
	opcode->set_op1(operand1);
	opcode->set_op2(operand2);
	opcode->set_handler(&vm::Engine::less_equal);
	
	addOpcode(opcode);
}

/**
 * Generates opcode for expressions like:
 * x - y
 * x -= y
 */
void Compiler::minus(Data& var, Data& value, Data* result, bool assign)
{
	opcode::Opcode* opcode;
	
	if (assign == false) {
		/**
		 * Only creates the temporary result space for non-assigning expression
		 * e.g. x - y
		 */
		NEW_RESULT_IF_NOT_EXISTS(var);
		SET_RESULT(*result, var.result);
		
		if (constant_folding("__minus__", var, value, result)) {
			return;
		}
	}
	opcode = new opcode::Opcode;
	opcode->set_type(assign ? opcode::TODO_ASSIGN_MINUS : opcode::TODO_MINUS);
	opcode->set_op1(var);
	opcode->set_op2(value);
	opcode->set_handler(&vm::Engine::minus);
	
	addOpcode(opcode);
}

/**
 * Gets the constant value from a Data
 */
inline Value* Compiler::get_constant_value(const Data& op) const
{
	if (op.data_type == TODO_CONSTANT) {
		return op.data;
	}
	return NULL;
}

/**
 * Clears a Data value
 */
inline void Compiler::clear_data_value(Data& op) const
{
	if (op.data_type == TODO_CONSTANT) {
		op.data->type->dealloc(op.data);
	}
}

/**
 * Trying to resolve constant expression
 */
inline bool Compiler::constant_folding(const std::string& method, Data& op1, Data& op2, Data* result) const
{
	Value* v_1;
	Value* v_2;

	if ((op1.data_type == TODO_CONSTANT && op2.data_type == TODO_CONSTANT)
		&& (v_1 = get_constant_value(op1)) != NULL && (v_2 = get_constant_value(op2)) != NULL) {
		ArgValues params;
	
		params.args.push_back(v_2);	
		
		result->data_type = TODO_CONSTANT;
		result->data = v_1->type->call_method(method, v_1, &params);

		clear_data_value(op1);
		clear_data_value(op2);
		return true;
	}
	return false;
}

/**
 * Generates opcode for expressions like:
 * x + y
 * x += y
 */
void Compiler::plus(Data& var, Data& value, Data* result, bool assign)
{
	opcode::Opcode* opcode;
	
	if (assign == false) {
		if (constant_folding("__plus__", var, value, result)) {
			return;
		}
		/**
		 * Only creates the temporary result space for non-assigning expression
		 * e.g. x + y
		 */
		NEW_RESULT_IF_NOT_EXISTS(var);
		SET_RESULT(*result, var.result);
	}
	opcode = new opcode::Opcode;
	opcode->set_type(assign ? opcode::TODO_ASSIGN_PLUS : opcode::TODO_PLUS);
	opcode->set_op1(var);
	opcode->set_op2(value);
	opcode->set_handler(&vm::Engine::plus);
	
	addOpcode(opcode);
}

/**
 * Generates opcode for expressions like:
 * x / y
 * x /= y
 */
void Compiler::div(Data& var, Data& value, Data* result, bool assign)
{
	opcode::Opcode* opcode;
	
	if (assign == false) {
		if (constant_folding("__div__", var, value, result)) {
			return;
		}
		/**
		 * Only creates the temporary result space for non-assigning expression
		 * e.g. x / y
		 */
		NEW_RESULT_IF_NOT_EXISTS(var);
		SET_RESULT(*result, var.result);
	}
	opcode = new opcode::Opcode;
	opcode->set_type(assign ? opcode::TODO_ASSIGN_DIV : opcode::TODO_DIV);
	opcode->set_op1(var);
	opcode->set_op2(value);
	opcode->set_handler(&vm::Engine::div);
	
	addOpcode(opcode);
}

/**
 * Generates opcode for expressions like:
 * x * y
 * x *= y
 */
void Compiler::mult(Data& var, Data& value, Data* result, bool assign)
{
	opcode::Opcode* opcode;
	
	if (assign == false) {
		if (constant_folding("__mult__", var, value, result)) {
			return;
		}
		/**
		 * Only creates the temporary result space for non-assigning expression
		 * e.g. x * y
		 */
		NEW_RESULT_IF_NOT_EXISTS(var);
		SET_RESULT(*result, var.result);
	}
	opcode = new opcode::Opcode;
	opcode->set_type(assign ? opcode::TODO_ASSIGN_MULT : opcode::TODO_MULT);
	opcode->set_op1(var);
	opcode->set_op2(value);
	opcode->set_handler(&vm::Engine::mult);
	
	addOpcode(opcode);
}

/**
 * Generates opcode for expressions like:
 * x % y
 * x %= y
 */
void Compiler::mod(Data& var, Data& value, Data* result, bool assign)
{
	opcode::Opcode* opcode;
	
	if (assign == false) {
		if (constant_folding("__mod__", var, value, result)) {
			return;
		}
		/**
		 * Only creates the temporary result space for non-assigning expression
		 * e.g. x % y
		 */
		NEW_RESULT_IF_NOT_EXISTS(var);
		SET_RESULT(*result, var.result);
	}
	opcode = new opcode::Opcode;
	opcode->set_type(assign ? opcode::TODO_ASSIGN_MOD : opcode::TODO_MOD);
	opcode->set_op1(var);
	opcode->set_op2(value);
	opcode->set_handler(&vm::Engine::mod);
	
	addOpcode(opcode);
}

/**
 * Assigns value to variable
 */
void Compiler::assign(Data& var, Data& value)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_ASSIGN);

	if (!check_type_var(STR_P(var.data), value)) {
		error("Incompatible type in assignment");
	}
	
	opcode->set_op1(var);
	opcode->set_op2(value);
	opcode->set_handler(&vm::Engine::assign);
	
	addOpcode(opcode);
}

inline bool Compiler::check_dead_condition(const Data& expr)
{
	const Value *val = get_constant_value(expr);
	
	if (val == NULL) {
		return false;
	}
	
	if (IS_TRUE(val)) {
		/**
		 * We have reached the condition that will be used,
		 * let's skip all others
		 */
		blk_states.top().done = true;
		return false;
	} else {
		/**
		 * The condition never will be true, let's skip the
		 * block content
		 */
		blk_states.top().dead = true;
		return true;
	}
}

void Compiler::if_cond(todo::Data& if_expr, todo::Data& result)
{
	opcode::Opcode* opcode;
	todo::Data jmp_op(TODO_CONSTANT, 0);
	std::stack<opcode::Opcode*> op;
	
	result.extra = get_op_num();
	
	if (if_expr.data_type == TODO_CONSTANT) {
		if (check_dead_condition(if_expr)) {
			clear_data_value(if_expr);
			jmps.push(std::stack<opcode::Opcode*>());
			return;
		}
	}
	opcode = new opcode::Opcode(opcode::TODO_JMPZ);
	opcode->set_op1(if_expr);
	opcode->set_op2(jmp_op);
	opcode->set_handler(&todo::vm::Engine::if_cond);
	
	op.push(opcode);
	jmps.push(op);
	
	addOpcode(opcode);
}

void Compiler::if_elsif(todo::Data& elsif_expr, todo::Data& result)
{
	opcode::Opcode* opcode;
	todo::Data jmp_op(TODO_CONSTANT, 0);
	std::stack<opcode::Opcode*> op;

	if (elsif_expr.data_type == TODO_CONSTANT) {
		if (check_dead_condition(elsif_expr)) {
			clear_data_value(elsif_expr);
			return;
		}
	}
	
	result.extra = get_op_num();
	
	opcode = new opcode::Opcode(opcode::TODO_JMPZ);
	opcode->set_op1(elsif_expr);
	opcode->set_op2(jmp_op);
	opcode->set_handler(&vm::Engine::if_cond);
	
	jmps.top().push(opcode);
	
	addOpcode(opcode);
}

void Compiler::if_end_block(todo::Data& cond_expr)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_JMP);
	todo::Data jmp_op(TODO_CONSTANT, 0);
	
	get_opcode(cond_expr.extra)->op2.extra = get_op_num();
	jmp_op.extra = get_op_num();
	
	opcode->set_op1(jmp_op);
	opcode->set_handler(&vm::Engine::jmp);
	
	jmps.top().push(opcode);
	
	addOpcode(opcode);
}

void Compiler::end_if(void)
{
	opcode::Opcode *op;
	unsigned int op_num = get_op_num()-1;

	if (!jmps.empty()) {
		while (!jmps.top().empty()) {
			op = jmps.top().top();
			op->op1.extra = op_num;
			jmps.top().pop();
		}
	}
}

void Compiler::equal(todo::Data& exp1, todo::Data& exp2)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_IS_EQUAL);
	
	if (exp1.data_type != todo::TODO_DYNAMIC) {
		exp1.result = new ValueResult;
	}
	
	opcode->set_op1(exp1);
	opcode->set_op2(exp2);
	opcode->set_handler(&vm::Engine::is_equal);
	
	addOpcode(opcode);
}

void Compiler::is_not_equal(todo::Data& exp1, todo::Data& exp2)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_IS_NOT_EQUAL);
	
	if (exp1.data_type != todo::TODO_DYNAMIC) {
		exp1.result = new ValueResult;
	}
	
	opcode->set_op1(exp1);
	opcode->set_op2(exp2);
	opcode->set_handler(&vm::Engine::is_not_equal);
	
	addOpcode(opcode);
}

inline unsigned int Compiler::get_op_num(void) const
{
	return opcode_list.size();
}

inline opcode::Opcode* Compiler::get_opcode(unsigned int num) const
{
	return opcode_list[num];
}

/**
 * Slice operator
 */
void Compiler::slice(todo::Data& object, todo::Data* first, todo::Data* second)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_SLICE);
	
	if (object.data_type != todo::TODO_DYNAMIC) {
		object.result = new todo::ValueResult;
		object.result_flag = todo::NONREFERENCED;
	}

	opcode->set_op1(object);
	opcode->set_handler(&todo::vm::Engine::slice);
	opcode->info = new ArgList;
	
	object.data_type = todo::TODO_DYNAMIC;
	
	if (first == NULL) {
		todo::Data* first_ = new Data(TODO_CONSTANT);
		todo::Data* second_ = new Data(*second);
		first_->data = NEW("int");
		
		// [:N] mode
		((ArgList*)opcode->info)->args.push_back(first_);
		((ArgList*)opcode->info)->args.push_back(second_);
	} else if (second == NULL) {
		todo::Data* first_ = new Data(*first);
		todo::Data* second_ = new Data(TODO_CONSTANT);
		second_->data = NEW("int");
		
		// [N:] mode
		((ArgList*)opcode->info)->args.push_back(first_);
		((ArgList*)opcode->info)->args.push_back(second_);
	} else {
		((ArgList*)opcode->info)->args.push_back(new todo::Data(*first));
		((ArgList*)opcode->info)->args.push_back(new todo::Data(*second));
	}	
	
	addOpcode(opcode);
}

/**
 * Generates opcode to exit statement
 * @param code the exit code
 */
void Compiler::exit_stmt(Data* code)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_EXIT);
	
	if (code) {
		opcode->set_op1(*code);
	}
	opcode->set_handler(&vm::Engine::exit_stmt);
	
	addOpcode(opcode);		
}

/**
 * Generates opcode to break statement
 */
void Compiler::break_stmt(Data* level)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_BREAK);
	Data jmp_op(TODO_CONSTANT, 0);
	
	opcode->set_op1(jmp_op);
	if (level) {
		if (INT_V(level->data) == 0 || (unsigned)INT_V(level->data) > brk_jmps.size()) {
			error("Invalid nested level!");
		}
		INT_V(level->data) = (brk_jmps.size() - INT_V(level->data)) + 1;
		opcode->set_op2(*level);
	}
	opcode->set_handler(&vm::Engine::jmp);
	
	brk_jmps.top().push(opcode);
	
	addOpcode(opcode);	
}

/**
 * Generates opcode to return statement
 * @param expr the value to be returned
 */
void Compiler::return_stmt(todo::Data* expr)
{
	opcode::Opcode* opcode = new opcode::Opcode(opcode::TODO_RETURN);
	
	if (expr) {
		opcode->set_op1(*expr);
	}
	opcode->set_handler(&vm::Engine::return_stmt);
	
	addOpcode(opcode);
}

/**
 * Initializes the execution
 */
void Compiler::finalize(void)
{
	if (!--count) {
		todo::vm::Engine(modules).run(opcode_list);
	}
}

/**
 * Clear opcode data
 */
void Compiler::shutdown(void)
{
	todo::vm::Engine(modules).shutdown(opcode_list);
}

void Compiler::import(Data& src) const
{
	Driver driver;

	driver.parse_file(STR_V(src.data));
	
	src.data->type->dealloc(src.data);
}

} // todo
